/*******************************************************************************
 * Copyright (c) 2010 L.Carbonnaux.
 *******************************************************************************/
package org.lcx.taskvision.core;

import java.io.IOException;
import java.net.URL;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.eclipse.core.runtime.Assert;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.Status;
import org.eclipse.mylyn.commons.net.AuthenticationCredentials;
import org.eclipse.mylyn.commons.net.AuthenticationType;
import org.eclipse.mylyn.tasks.core.IRepositoryQuery;
import org.eclipse.mylyn.tasks.core.TaskRepository;
import org.eclipse.mylyn.tasks.core.data.TaskAttribute;
import org.eclipse.mylyn.tasks.core.data.TaskData;
import org.eclipse.mylyn.tasks.core.data.TaskDataCollector;
import org.eclipse.osgi.util.NLS;

import org.lcx.taskvision.core.attribute.TaskVisionAttribute;
import org.lcx.taskvision.core.handler.TaskVisionTaskDataHandler;
import org.lcx.taskvision.core.model.TaskVisionSpreadSheet;
import org.lcx.taskvision.core.model.TaskVisionWorkSheet;
import org.lcx.taskvision.core.util.TaskVisionUtil;

import com.google.gdata.client.docs.DocsService;
import com.google.gdata.client.spreadsheet.ListQuery;
import com.google.gdata.client.spreadsheet.SpreadsheetService;
import com.google.gdata.data.spreadsheet.ListEntry;
import com.google.gdata.data.spreadsheet.ListFeed;
import com.google.gdata.data.spreadsheet.SpreadsheetEntry;
import com.google.gdata.data.spreadsheet.SpreadsheetFeed;
import com.google.gdata.data.spreadsheet.WorksheetEntry;
import com.google.gdata.data.spreadsheet.WorksheetFeed;
import com.google.gdata.util.AuthenticationException;
import com.google.gdata.util.ServiceException;

/**
 * @author Laurent Carbonnaux
 */
public class TaskVisionClient {

	private String username;

	private String password;

	private TaskRepository taskRepository;
	
	SpreadsheetService 	spreadsheetService;
	SpreadsheetService 	spreadsheetServiceForBatch;
	DocsService  		docsService;


//	private static SimpleDateFormat sdf = new SimpleDateFormat("M/d/yyyy");
	
	public TaskVisionClient(TaskRepository taskRepository) throws AuthenticationException, CoreException {
		setTaskRepository(taskRepository);
		getSpreadsheetService().setUserCredentials(username, password);
		getDocsService().setUserCredentials(username, password);
	}

	/**
	 * Called when synchronization of all query or new query is requested
	 */
	public void performQuery(TaskRepository repository,
			IRepositoryQuery query, TaskDataCollector resultCollector, IProgressMonitor monitor) throws IOException, ServiceException, CoreException {
			List<ListEntry> entries = new ArrayList<ListEntry>();
			if(query.getUrl()!=null) {
				this.getListTasks(query, entries);
				for (ListEntry listEntry : entries) {
					TaskVisionRepositoryConnector connector = TaskVisionCorePlugin.getConnector(repository);
					TaskVisionTaskDataHandler handler = connector.getTaskDataHandler();
					TaskData taskData = handler.createTaskDataFromListEntry(repository, listEntry, monitor);
					resultCollector.accept(taskData);
				}
			}
	}
	
	/**
	 * Perform a query for the task list view
	 * @param query
	 * @param collector
	 * @throws IOException
	 */
	public void getListTasks(IRepositoryQuery query, List<ListEntry> resultCollector) throws IOException, ServiceException {
		String urlQuery = query.getUrl();
		URL url = TaskVisionUtil.getRepositoryListUrl(this.taskRepository);
	    if (url!= null && urlQuery!=null && resultCollector!=null) {
		    ListQuery lquery = new ListQuery(url);
	    	lquery.setSpreadsheetQuery(urlQuery);
		    ListFeed taskList = getSpreadsheetService().query(lquery, ListFeed.class);
		    for (ListEntry task : taskList.getEntries()) {
		    	resultCollector.add(task);
			}
	    }
	}
	
	/**
	 * To retrieve the list of available spreadsheets for the given repository url
	 * Used in the task repository wizard
	 * @param url
	 * @param collector
	 * @throws IOException
	 */
	public void getListSpreadSheets(URL url, List<TaskVisionSpreadSheet> collector) throws IOException {

		try {
			SpreadsheetFeed feed = getSpreadsheetService().getFeed(url, SpreadsheetFeed.class);
			List<SpreadsheetEntry> spreadsheets = feed.getEntries();
			for (SpreadsheetEntry spreadsheet : spreadsheets) {
				TaskVisionSpreadSheet sp = new TaskVisionSpreadSheet(spreadsheet);
				List<WorksheetEntry> entries = spreadsheet.getWorksheets();
				for (WorksheetEntry worksheetEntry : entries) {
					TaskVisionWorkSheet worksheet = new TaskVisionWorkSheet(worksheetEntry);
					sp.getWorksheets().add(worksheet);
				}

				collector.add(sp);
			}
			
		} catch (ServiceException e) {
			e.printStackTrace();
			throw new IOException(e.getMessage() + NLS.bind(Messages.ScrumVisionClient_repository_label, taskRepository.getRepositoryLabel()));
		}
	}

	/**
	 * To retrieve the available tag values for the setting filter of a task query
	 * @param url
	 * @param collector
	 * @throws IOException
	 */
	public void getListTags(Map<String, Set<String>> collector) throws IOException {

		try {
			URL url = new URL(taskRepository.getRepositoryUrl());
		    ListQuery query = new ListQuery(url);
		    query.setSpreadsheetQuery(ITaskVisionClient.ALL_TASKS_QUERY);
		    ListFeed feedList = getSpreadsheetService().query(query, ListFeed.class);
	    	Set<String> priorities = new HashSet<String>();
	    	Set<String> statuses = new HashSet<String>();

	    	for (ListEntry feed : feedList.getEntries()) {
	    		//for other fixed tags
	    		
	    		String priority = feed.getCustomElements().getValue(TaskVisionAttribute.PRIORITY.getSvKey());
	    		if(priority!=null) priorities.add(priority);
	    		String status = feed.getCustomElements().getValue(TaskVisionAttribute.COMPLETED.getSvKey());
	    		if(status != null) statuses.add(status);
		    }

	    	if(priorities.size()!=0) collector.put(TaskVisionAttribute.PRIORITY.getSvKey(), priorities);
	    	if(statuses.size()!=0) collector.put(TaskVisionAttribute.COMPLETED.getSvKey(), statuses);
		
		} catch (ServiceException e) {
			e.printStackTrace();
			throw new IOException(e.getMessage() + NLS.bind(Messages.ScrumVisionClient_repository_label, taskRepository.getRepositoryLabel()));
		}
		
	}
	
	
	/**
	 * To retrieve the list of available worksheets for the given spreadsheet url
	 * Used in the task repository wizard
	 * @param url
	 * @param collector
	 * @throws IOException
	 */
	public void getListWorkSheets(URL url, List<TaskVisionWorkSheet> collector) throws IOException  {

		try {
			WorksheetFeed feed = getSpreadsheetService().getFeed(url, WorksheetFeed.class);
			List<WorksheetEntry> worksheets = feed.getEntries();
			for (WorksheetEntry worksheet : worksheets) {
				collector.add(new TaskVisionWorkSheet(worksheet));
			}
			
		} catch (ServiceException e) {
			e.printStackTrace();
			throw new IOException(e.getMessage() + NLS.bind(Messages.ScrumVisionClient_repository_label, taskRepository.getRepositoryLabel()));
		}
	}
	
	/**
	 * Retrieve a task from the spreadsheet for the given taskId
	 * @param taskId
	 * @return TaskData
	 * @throws IOException
	 */
	public TaskData getTaskData(TaskRepository repository, String taskId, IProgressMonitor monitor) throws IOException, ServiceException, CoreException {
		URL url = TaskVisionUtil.getRepositoryListUrl(this.taskRepository);
	    ListQuery query = new ListQuery(url);
	    query.setSpreadsheetQuery(TaskVisionAttribute.TASKID.getSvKey()+"=\""+taskId+"\"");
	    ListFeed taskList = getSpreadsheetService().query(query, ListFeed.class);
	    
	    for (ListEntry task : taskList.getEntries()) {
	    	if(taskId.equals(TaskVisionUtil.getTaskIdFromEntry(task))){
	    		TaskVisionTaskDataHandler handler = TaskVisionCorePlugin.getConnector(repository).getTaskDataHandler();
				TaskData taskData = handler.createTaskDataFromListEntry(repository, task, monitor);
		    	return taskData; // should be only one task with this ID
	    	}
		}
		return null;
	}

	/**
	 * Delete a task in the backlog
	 * @param taskId
	 * @throws IOException
	 */
	public void deleteTaskData(String taskId) throws CoreException {
		try {
			URL url = TaskVisionUtil.getRepositoryListUrl(this.taskRepository);
		    ListQuery query = new ListQuery(url);
		    query.setSpreadsheetQuery(TaskVisionAttribute.TASKID.getSvKey()+"=\""+taskId+"\"");
		    ListFeed taskList = getSpreadsheetService().query(query, ListFeed.class);
		    
		    for (ListEntry task : taskList.getEntries()) {
		    	if(taskId.equals(TaskVisionUtil.getTaskIdFromEntry(task))){
		    		task.delete();
		    	}
			}

		}
		catch (AuthenticationException e) {
			e.printStackTrace();
			throw new CoreException(new Status(Status.ERROR, TaskVisionCorePlugin.PLUGIN_ID, 1,
					NLS.bind(Messages.ScrumVisionClientManager_invalid_credential, taskRepository.getRepositoryLabel()), e));
		}
		catch (Exception e) {
			throw new CoreException(new Status(Status.ERROR, TaskVisionCorePlugin.PLUGIN_ID, 1,
					NLS.bind(Messages.ScrumVisionClient_repository_label, taskRepository.getRepositoryLabel(), e.getMessage()), e));
		}
	}
	
	/**
	 * Send task updates to the backlog for the given Mylyn taskData
	 * If task is new, task is created
	 * else task is updated
	 * @param taskData
	 * @return id of the task
	 * @throws CoreException
	 */
	public String postTaskData(TaskData taskData) throws CoreException {
		try {
			long t1 = System.currentTimeMillis();
			long t2 = System.currentTimeMillis();

			String taskId = taskData.getTaskId();
			if(taskData.isNew()) {
				taskId = insertNewTask(taskData);
			} else {
				updateExistingTask(taskData);
			}
			long t3 = System.currentTimeMillis();
			TaskVisionCorePlugin.log(new Status(Status.INFO, TaskVisionCorePlugin.PLUGIN_ID, "all="+ (t2-t1)));
			TaskVisionCorePlugin.log(new Status(Status.INFO, TaskVisionCorePlugin.PLUGIN_ID, "one="+ (t3-t2)));

			return taskId;
		} catch (AuthenticationException e) {
			e.printStackTrace();
			throw new CoreException(new Status(Status.ERROR, TaskVisionCorePlugin.PLUGIN_ID, 1,
					NLS.bind(Messages.ScrumVisionClientManager_invalid_credential, taskRepository.getRepositoryLabel()), e));
		} catch (CoreException e) {
			throw e;
		} catch (Exception e) {
			e.printStackTrace(System.err);
			throw new CoreException(new Status(Status.ERROR, TaskVisionCorePlugin.PLUGIN_ID, 1,
					NLS.bind(Messages.ScrumVisionClient_repository_label, taskRepository.getRepositoryLabel(), e.getMessage()), e));
		}
	}
	
	/**
	 * Update spreadsheet task with data from Mylyn
	 * @param taskData
	 * @throws Exception
	 */
	public void updateExistingTask(TaskData taskData) throws CoreException, IOException, ServiceException  {
		String taskId = taskData.getTaskId();

		URL url = TaskVisionUtil.getRepositoryListUrl(this.taskRepository);

		ListQuery query = new ListQuery(url);
	    query.setSpreadsheetQuery(TaskVisionAttribute.TASKID.getSvKey()+"=\""+taskId+"\"");
	    ListFeed taskList = getSpreadsheetService().query(query, ListFeed.class);

	    for (ListEntry task : taskList.getEntries()) {
	    	// be sure it's the correct task
	    	if(taskId.equals(TaskVisionUtil.getTaskIdFromEntry(task))){
	    		updateFeedFromTask(taskData, task);
	    		task.update();
		    	break; // should be only one task with this ID
	    	}
		}
	}
	
	/**
	 * Insert the new task into the spreadsheet with data from Mylyn taskData
	 * @param taskData
	 * @return
	 * @throws Exception
	 */
	public String insertNewTask(TaskData taskData) throws CoreException, IOException, ServiceException {
		URL url = TaskVisionUtil.getRepositoryListUrl(this.taskRepository);

		ListQuery query = new ListQuery(url);
	    query.setSpreadsheetQuery(ITaskVisionClient.ALL_TASKS_QUERY);
	    
	    ListFeed taskList = getSpreadsheetService().query(query, ListFeed.class);
	    int maxId = 0;
	    for (ListEntry task : taskList.getEntries()) {
	    	String id = TaskVisionUtil.getTaskIdFromEntry(task);
	    	int i = Integer.parseInt(id);
	    	if(i>maxId) maxId = i;
	    }
	    maxId++;
	    String taskId = ""+maxId; 
	    ListEntry newTask = taskList.createEntry();

		updateFeedFromTask(taskData, newTask);
		TaskVisionUtil.setTaskId(newTask, taskId);
		getSpreadsheetService().insert(query.getUrl(), newTask);
		return taskId;
	}
	
	/**
	 * Update the feed task data with one from Mylyn task
	 * and set the remaining hours at the today iteration day (lastremKey)
	 * @param conf, ScrumVision preference
	 * @param taskData, the task from editor
	 * @param task, the task to update in the google feed
	 * @throws CoreException
	 */
	private void updateFeedFromTask(TaskData taskData, ListEntry task) throws CoreException {
		// setting the remaining hours at the last correct date
		Assert.isNotNull(taskData);
		Assert.isNotNull(task);
		
		Map<String, TaskAttribute> mapAtt = taskData.getRoot().getAttributes();
		for (TaskAttribute attribute : mapAtt.values()) {
			String value = attribute.getValue();
			if(value!=null){
				String key = attribute.getId();
				task.getCustomElements().setValueLocal(key, value);
			}
		}
	}
	
	public String getUsername() {
		return username;
	}

	public String getPassword() {
		return password;
	}

	public URL getRepositoryUrlFromSprint(String prefix) {
		return TaskVisionUtil.getRepositoryListUrl(this.getTaskRepository());
	}

	public TaskRepository getTaskRepository() {
		return taskRepository;
	}

	public void setTaskRepository(TaskRepository taskRepository) throws CoreException {
		this.taskRepository = taskRepository;
		AuthenticationCredentials credentials = taskRepository.getCredentials(AuthenticationType.REPOSITORY);
		if (credentials != null) {
			this.username = credentials.getUserName();
			this.password = credentials.getPassword();
		}
	}

	public SpreadsheetService getSpreadsheetService() {
		if(spreadsheetService==null){
			spreadsheetService = new SpreadsheetService("TaskVision Service");
		}
		return spreadsheetService;
	}

	public DocsService getDocsService() {
		if(docsService==null){
			docsService = new DocsService("ScrumVision Service");
		}
		return docsService;
	}

}
